"""
基于 structlog 的统一日志工具.

提供结构化的日志记录功能, 支持 JSON 和控制台两种输出格式,
自动包含应用上下文信息。
"""

import atexit
import logging
import logging.handlers
import sys
from datetime import datetime
from queue import Queue
from time import time_ns
from typing import Final

import orjson
import structlog
from opentelemetry._logs import get_logger_provider
from opentelemetry._logs._internal import ProxyLogger
from opentelemetry.context import get_current
from opentelemetry.sdk._logs import LogRecord
from opentelemetry.sdk._logs._internal import LoggerProvider, std_to_otel
from structlog.processors import NAME_TO_LEVEL

from app.core.config import settings


class SLQueueHandler(logging.handlers.QueueHandler):
    """Structlog 队列处理器."""

    def prepare(self, record: logging.LogRecord) -> object:
        """准备记录."""
        return record


class StructlogHandler:
    """Structlog 处理器."""

    _dumps: Final = orjson.dumps
    """JSON 序列化器."""

    _EXCLUDE_ATTRS: Final = {"event", "exception", "timestamp", "logger", "level"}
    """排除的属性."""

    def __init__(self, logger_provider: LoggerProvider | None = None) -> None:
        """
        初始化 Structlog 处理器.

        Args:
            logger_provider: 日志记录器提供者, 如果不提供则使用默认提供者

        """
        self._logger_provider = (
            get_logger_provider() if logger_provider is None else logger_provider
        )

    def __call__(
        self,
        logger: structlog.typing.WrappedLogger,
        name: str,
        event_dict: structlog.typing.EventDict,
    ) -> structlog.typing.EventDict:
        """
        处理日志记录.

        Args:
            logger: 日志记录器
            name: 日志记录器名称
            event_dict: 事件字典

        """
        logger = self._logger_provider.get_logger(name)

        if isinstance(logger, ProxyLogger):
            return event_dict

        observered_timestamp = time_ns()
        timestamp = int(
            datetime.fromisoformat(event_dict["timestamp"]).timestamp() * 1e9
        )
        attributes = {
            k: v if isinstance(v, (bool, str, bytes, int, float)) else self._dumps(v)
            for k, v in event_dict.items()
            if k not in self._EXCLUDE_ATTRS and not k.startswith("_")
        }

        logger.emit(
            LogRecord(
                timestamp=timestamp,
                observed_timestamp=observered_timestamp,
                context=get_current() or None,
                severity_text=event_dict["level"],
                severity_number=std_to_otel(NAME_TO_LEVEL[event_dict["level"]]),
                body=event_dict["event"],
                resource=logger.resource,
                attributes=attributes,
            )
        )

        return event_dict


def get_logger(name: str) -> structlog.stdlib.BoundLogger:
    """
    获取指定名称的日志记录器.

    Args:
        name: 日志记录器名称, 通常是模块的 __name__

    Returns:
        structlog 绑定日志记录器

    """
    return structlog.get_logger(name)


def _configure_logging() -> None:
    """配置日志记录器."""
    # 共享处理器
    shared_processors = [
        structlog.stdlib.add_log_level,  # 添加日志级别
        structlog.stdlib.add_logger_name,  # 添加 logger 名称
        structlog.processors.TimeStamper(fmt="iso"),  # 添加时间戳
        structlog.processors.StackInfoRenderer(),  # 添加堆栈信息
        structlog.processors.format_exc_info,  # 添加异常信息
        structlog.contextvars.merge_contextvars,  # 添加上下文变量
    ]

    # OpenTelemetry 处理器
    if settings.OTEL_ENABLED:
        shared_processors.append(StructlogHandler())

    # 配置 structlog
    structlog.configure(
        processors=[
            *shared_processors,
            # 标记不需要再次包装
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.BoundLogger,
        cache_logger_on_first_use=True,
    )

    # 处理器格式化器
    processor_formatter = structlog.stdlib.ProcessorFormatter(
        foreign_pre_chain=shared_processors,  # 处理非 structlog 日志
        processors=[
            # 移除处理器元数据
            structlog.stdlib.ProcessorFormatter.remove_processors_meta,
            (
                # 使用 JSON 渲染器
                structlog.processors.JSONRenderer(
                    lambda obj, **kw: orjson.dumps(obj, **kw).decode("utf-8")
                )
                if not settings.DEBUG
                # 使用控制台渲染器
                else structlog.dev.ConsoleRenderer(colors=True)
            ),
        ],
    )

    # 根日志器
    root = logging.getLogger()
    root.setLevel(settings.LOG_LEVEL.upper())
    root.handlers.clear()

    # 接管 uvicorn 的日志
    for name in ("uvicorn", "uvicorn.error", "uvicorn.access"):
        lg = logging.getLogger(name)
        lg.handlers.clear()
        lg.propagate = True

    # 添加 Queue 处理器
    log_queue = Queue(-1)
    queue_handler = SLQueueHandler(log_queue)
    root.addHandler(queue_handler)

    handlers = []

    # 添加 stdout 处理器
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setFormatter(processor_formatter)
    handlers.append(stdout_handler)

    # 添加文件处理器
    if settings.LOG_FILE:
        # 保证目录存在
        settings.LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
        file_handler = logging.FileHandler(settings.LOG_FILE, encoding="utf-8")
        file_handler.setFormatter(processor_formatter)
        handlers.append(file_handler)

    # 监听队列, 同时保证退出时清空队列
    listener = logging.handlers.QueueListener(
        log_queue, *handlers, respect_handler_level=True
    )
    listener.start()
    atexit.register(listener.stop)


# 配置日志记录器
_configure_logging()
